﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using SimpleFMMatrix.DataStructures;

namespace SimpleFMMatrix.ViewModel
{
    public class EnvelopeVM : DependencyObject
    {
        private Envelope Envelope { get; set; }

        public ValuedDependencyObject KeyOnLevel { get; private set; }
        public ValuedDependencyObject AttackSlope { get; private set; }
        public ValuedDependencyObject AttackDuration { get; private set; }
        public ValuedDependencyObject PeakLevel { get; private set; }
        public ValuedDependencyObject DecaySlope { get; private set; }
        public ValuedDependencyObject DecayDuration { get; private set; }
        public ValuedDependencyObject SustainLevel { get; private set; }
        public ValuedDependencyObject ReleaseSlope { get; private set; }
        public ValuedDependencyObject ReleaseDuration { get; private set; }
        public ValuedDependencyObject KeyOffLevel { get; private set; }

        public ADSRCurveDependencyObject AttackCurves { get; private set; }
        public ADSRCurveDependencyObject DecayCurves { get; private set; }
        public ADSRCurveDependencyObject ReleaseCurves { get; private set; }

        public ValuedDependencyObject HZoom { get; private set; }
        public ValuedDependencyObject Height { get; private set; }

        private void UpdateCurve(ADSRCurveDependencyObject target, double startlevel, EnvelopePhase phase)
        {
            var increment = 5 / Height.Value;
            if (startlevel > phase.EndLevel) increment = -increment;
            target.ValueIncrement = increment;

            var xscale = Math.Pow(2, HZoom.Value);
            var yscale = Height.Value;
            Func<double, double, Point> scale = (x, y) =>
                new Point(x * xscale, (1 - y) * yscale);

            var cur = new StreamGeometry();
            var ccur = new StreamGeometry();

            using (StreamGeometryContext c = cur.Open(), cc = ccur.Open())
            {
                var startp = scale(0, startlevel);
                c.BeginFigure(startp, true, false);
                cc.BeginFigure(startp, true, true);

                for (int i = 1; i <= 64; i++)
                {
                    var t = i * phase.Duration / 64.0;
                    var p = scale(t, phase.AmplitudeAt(startlevel, t));
                    c.LineTo(p, true, false);
                    cc.LineTo(p, true, false);
                }

                cc.LineTo(scale(phase.Duration, 0), true, false);
                cc.LineTo(scale(0, 0), true, false);
            }

            cur.Freeze();
            ccur.Freeze();

            target.Curve = cur;
            target.ClosedCurve = ccur;

            target.CurveWidth = scale(phase.Duration, 0).X;
        }

        internal EnvelopeVM(Envelope e)
        {
            Envelope = e;

            Action updateAttack = () => UpdateCurve(AttackCurves, e.KeyOnLevel, e.Attack);
            Action updateDecay = () => UpdateCurve(DecayCurves, e.Attack.EndLevel, e.Decay);
            Action updateRelease = () => UpdateCurve(ReleaseCurves, e.Decay.EndLevel, e.Release);

            Action updateAll = () =>
            {
                updateAttack();
                updateDecay();
                updateRelease();
            };

            AttackCurves = new ADSRCurveDependencyObject();
            DecayCurves = new ADSRCurveDependencyObject();
            ReleaseCurves = new ADSRCurveDependencyObject();

            KeyOnLevel = new ValuedDependencyObject(e.KeyOnLevel, (o, n) =>
            {
                e.KeyOnLevel = n;
                updateAttack();
            });

            AttackSlope = new ValuedDependencyObject(e.Attack.Slope, (o, n) =>
            {
                e.Attack.Slope = n;
                updateAttack();
            });

            AttackDuration = new ValuedDependencyObject(e.Attack.Duration, (o, n) =>
            {
                e.Attack.Duration = n;
                updateAttack();
            });

            PeakLevel = new ValuedDependencyObject(e.Attack.EndLevel, (o, n) =>
            {
                e.Attack.EndLevel = n;
                updateAttack();
                updateDecay();
            });

            DecaySlope = new ValuedDependencyObject(e.Decay.Slope, (o, n) =>
            {
                e.Decay.Slope = n;
                updateDecay();
            });

            DecayDuration = new ValuedDependencyObject(e.Decay.Duration, (o, n) =>
            {
                e.Decay.Duration = n;
                updateDecay();
            });

            SustainLevel = new ValuedDependencyObject(e.Decay.EndLevel, (o, n) =>
            {
                e.Decay.EndLevel = n;
                updateDecay();
                updateRelease();
            });

            ReleaseSlope = new ValuedDependencyObject(e.Release.Slope, (o, n) =>
            {
                e.Release.Slope = n;
                updateRelease();
            });

            ReleaseDuration = new ValuedDependencyObject(e.Release.Duration, (o, n) =>
            {
                e.Release.Duration = n;
                updateRelease();
            });

            KeyOffLevel = new ValuedDependencyObject(e.Release.EndLevel, (o, n) =>
            {
                e.Release.EndLevel = n;
                updateRelease();
            });

            HZoom = new ValuedDependencyObject(8, (o, n) => updateAll());

            Height = new ValuedDependencyObject(50, (o, n) => updateAll());

            updateAll();
        }
    }
}
